<?php

/**
 * Implements the OpenID attribute exchange specification, version 1.0
 * as of svn revision 370 from openid.net svn.
 *
 * @package OpenID
 */

// Do not allow direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

/**
 * Require utility classes and functions for the consumer.
 */
require_once "Auth/OpenID/Extension.php";
require_once "Auth/OpenID/Message.php";
require_once "Auth/OpenID/TrustRoot.php";

define('Auth_OpenID_AX_NS_URI',
	   'http://openid.net/srv/ax/1.0');

// Use this as the 'count' value for an attribute in a FetchRequest to
// ask for as many values as the OP can provide.
define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');

// Minimum supported alias length in characters.  Here for
// completeness.
define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);

/**
 * AX utility class.
 *
 * @package OpenID
 */
class Auth_OpenID_AX {
	/**
	 * @param mixed $thing Any object which may be an
	 * Auth_OpenID_AX_Error object.
	 *
	 * @return bool true if $thing is an Auth_OpenID_AX_Error; false
	 * if not.
	 */
	function isError($thing)
	{
		return is_a($thing, 'Auth_OpenID_AX_Error');
	}
}

/**
 * Check an alias for invalid characters; raise AXError if any are
 * found.  Return None if the alias is valid.
 */
function Auth_OpenID_AX_checkAlias($alias)
{
  if (strpos($alias, ',') !== false) {
	  return new Auth_OpenID_AX_Error(sprintf(
				   "Alias %s must not contain comma", $alias));
  }
  if (strpos($alias, '.') !== false) {
	  return new Auth_OpenID_AX_Error(sprintf(
				   "Alias %s must not contain period", $alias));
  }

  return true;
}

/**
 * Results from data that does not meet the attribute exchange 1.0
 * specification
 *
 * @package OpenID
 */
class Auth_OpenID_AX_Error {
	function Auth_OpenID_AX_Error($message=null)
	{
		$this->message = $message;
	}
}

/**
 * Abstract class containing common code for attribute exchange
 * messages.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
	/**
	 * ns_alias: The preferred namespace alias for attribute exchange
	 * messages
	 */
	var $ns_alias = 'ax';

	/**
	 * mode: The type of this attribute exchange message. This must be
	 * overridden in subclasses.
	 */
	var $mode = null;

	var $ns_uri = Auth_OpenID_AX_NS_URI;

	/**
	 * Return Auth_OpenID_AX_Error if the mode in the attribute
	 * exchange arguments does not match what is expected for this
	 * class; true otherwise.
	 *
	 * @access private
	 */
	function _checkMode($ax_args)
	{
		$mode = Auth_OpenID::arrayGet($ax_args, 'mode');
		if ($mode != $this->mode) {
			return new Auth_OpenID_AX_Error(
							sprintf(
									"Expected mode '%s'; got '%s'",
									$this->mode, $mode));
		}

		return true;
	}

	/**
	 * Return a set of attribute exchange arguments containing the
	 * basic information that must be in every attribute exchange
	 * message.
	 *
	 * @access private
	 */
	function _newArgs()
	{
		return array('mode' => $this->mode);
	}
}

/**
 * Represents a single attribute in an attribute exchange
 * request. This should be added to an AXRequest object in order to
 * request the attribute.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_AttrInfo {
	/**
	 * Construct an attribute information object.  Do not call this
	 * directly; call make(...) instead.
	 *
	 * @param string $type_uri The type URI for this attribute.
	 *
	 * @param int $count The number of values of this type to request.
	 *
	 * @param bool $required Whether the attribute will be marked as
	 * required in the request.
	 *
	 * @param string $alias The name that should be given to this
	 * attribute in the request.
	 */
	function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
									 $alias)
	{
		/**
		 * required: Whether the attribute will be marked as required
		 * when presented to the subject of the attribute exchange
		 * request.
		 */
		$this->required = $required;

		/**
		 * count: How many values of this type to request from the
		 * subject. Defaults to one.
		 */
		$this->count = $count;

		/**
		 * type_uri: The identifier that determines what the attribute
		 * represents and how it is serialized. For example, one type
		 * URI representing dates could represent a Unix timestamp in
		 * base 10 and another could represent a human-readable
		 * string.
		 */
		$this->type_uri = $type_uri;

		/**
		 * alias: The name that should be given to this attribute in
		 * the request. If it is not supplied, a generic name will be
		 * assigned. For example, if you want to call a Unix timestamp
		 * value 'tstamp', set its alias to that value. If two
		 * attributes in the same message request to use the same
		 * alias, the request will fail to be generated.
		 */
		$this->alias = $alias;
	}

	/**
	 * Construct an attribute information object.  For parameter
	 * details, see the constructor.
	 */
	function make($type_uri, $count=1, $required=false,
				  $alias=null)
	{
		if ($alias !== null) {
			$result = Auth_OpenID_AX_checkAlias($alias);

			if (Auth_OpenID_AX::isError($result)) {
				return $result;
			}
		}

		return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
										   $alias);
	}

	/**
	 * When processing a request for this attribute, the OP should
	 * call this method to determine whether all available attribute
	 * values were requested.  If self.count == UNLIMITED_VALUES, this
	 * returns True.  Otherwise this returns False, in which case
	 * self.count is an integer.
	*/
	function wantsUnlimitedValues()
	{
		return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
	}
}

/**
 * Given a namespace mapping and a string containing a comma-separated
 * list of namespace aliases, return a list of type URIs that
 * correspond to those aliases.
 *
 * @param $namespace_map The mapping from namespace URI to alias
 * @param $alias_list_s The string containing the comma-separated
 * list of aliases. May also be None for convenience.
 *
 * @return $seq The list of namespace URIs that corresponds to the
 * supplied list of aliases. If the string was zero-length or None, an
 * empty list will be returned.
 *
 * return null If an alias is present in the list of aliases but
 * is not present in the namespace map.
 */
function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s)
{
	$uris = array();

	if ($alias_list_s) {
		foreach (explode(',', $alias_list_s) as $alias) {
			$type_uri = $namespace_map->getNamespaceURI($alias);
			if ($type_uri === null) {
				// raise KeyError(
				// 'No type is defined for attribute name %r' % (alias,))
				return new Auth_OpenID_AX_Error(
				  sprintf('No type is defined for attribute name %s',
						  $alias)
				  );
			} else {
				$uris[] = $type_uri;
			}
		}
	}

	return $uris;
}

/**
 * An attribute exchange 'fetch_request' message. This message is sent
 * by a relying party when it wishes to obtain attributes about the
 * subject of an OpenID authentication request.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {

	var $mode = 'fetch_request';

	function Auth_OpenID_AX_FetchRequest($update_url=null)
	{
		/**
		 * requested_attributes: The attributes that have been
		 * requested thus far, indexed by the type URI.
		 */
		$this->requested_attributes = array();

		/**
		 * update_url: A URL that will accept responses for this
		 * attribute exchange request, even in the absence of the user
		 * who made this request.
		*/
		$this->update_url = $update_url;
	}

	/**
	 * Add an attribute to this attribute exchange request.
	 *
	 * @param attribute: The attribute that is being requested
	 * @return true on success, false when the requested attribute is
	 * already present in this fetch request.
	 */
	function add($attribute)
	{
		if ($this->contains($attribute->type_uri)) {
			return new Auth_OpenID_AX_Error(
			  sprintf("The attribute %s has already been requested",
					  $attribute->type_uri));
		}

		$this->requested_attributes[$attribute->type_uri] = $attribute;

		return true;
	}

	/**
	 * Get the serialized form of this attribute fetch request.
	 *
	 * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters
	 */
	function getExtensionArgs()
	{
		$aliases = new Auth_OpenID_NamespaceMap();

		$required = array();
		$if_available = array();

		$ax_args = $this->_newArgs();

		foreach ($this->requested_attributes as $type_uri => $attribute) {
			if ($attribute->alias === null) {
				$alias = $aliases->add($type_uri);
			} else {
				$alias = $aliases->addAlias($type_uri, $attribute->alias);

				if ($alias === null) {
					return new Auth_OpenID_AX_Error(
					  sprintf("Could not add alias %s for URI %s",
							  $attribute->alias, $type_uri
					  ));
				}
			}

			if ($attribute->required) {
				$required[] = $alias;
			} else {
				$if_available[] = $alias;
			}

			if ($attribute->count != 1) {
				$ax_args['count.' . $alias] = strval($attribute->count);
			}

			$ax_args['type.' . $alias] = $type_uri;
		}

		if ($required) {
			$ax_args['required'] = implode(',', $required);
		}

		if ($if_available) {
			$ax_args['if_available'] = implode(',', $if_available);
		}

		return $ax_args;
	}

	/**
	 * Get the type URIs for all attributes that have been marked as
	 * required.
	 *
	 * @return A list of the type URIs for attributes that have been
	 * marked as required.
	 */
	function getRequiredAttrs()
	{
		$required = array();
		foreach ($this->requested_attributes as $type_uri => $attribute) {
			if ($attribute->required) {
				$required[] = $type_uri;
			}
		}

		return $required;
	}

	/**
	 * Extract a FetchRequest from an OpenID message
	 *
	 * @param request: The OpenID request containing the attribute
	 * fetch request
	 *
	 * @returns mixed An Auth_OpenID_AX_Error or the
	 * Auth_OpenID_AX_FetchRequest extracted from the request message if
	 * successful
	 */
	function &fromOpenIDRequest($request)
	{
		$m = $request->message;
		$obj = new Auth_OpenID_AX_FetchRequest();
		$ax_args = $m->getArgs($obj->ns_uri);

		$result = $obj->parseExtensionArgs($ax_args);

		if (Auth_OpenID_AX::isError($result)) {
			return $result;
		}

		if ($obj->update_url) {
			// Update URL must match the openid.realm of the
			// underlying OpenID 2 message.
			$realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm',
						$m->getArg(
								  Auth_OpenID_OPENID_NS,
								  'return_to'));

			if (!$realm) {
				$obj = new Auth_OpenID_AX_Error(
				  sprintf("Cannot validate update_url %s " .
						  "against absent realm", $obj->update_url));
			} else if (!Auth_OpenID_TrustRoot::match($realm,
													 $obj->update_url)) {
				$obj = new Auth_OpenID_AX_Error(
				  sprintf("Update URL %s failed validation against realm %s",
						  $obj->update_url, $realm));
			}
		}

		return $obj;
	}

	/**
	 * Given attribute exchange arguments, populate this FetchRequest.
	 *
	 * @return $result Auth_OpenID_AX_Error if the data to be parsed
	 * does not follow the attribute exchange specification. At least
	 * when 'if_available' or 'required' is not specified for a
	 * particular attribute type.  Returns true otherwise.
	*/
	function parseExtensionArgs($ax_args)
	{
		$result = $this->_checkMode($ax_args);
		if (Auth_OpenID_AX::isError($result)) {
			return $result;
		}

		$aliases = new Auth_OpenID_NamespaceMap();

		foreach ($ax_args as $key => $value) {
			if (strpos($key, 'type.') === 0) {
				$alias = substr($key, 5);
				$type_uri = $value;

				$alias = $aliases->addAlias($type_uri, $alias);

				if ($alias === null) {
					return new Auth_OpenID_AX_Error(
					  sprintf("Could not add alias %s for URI %s",
							  $alias, $type_uri)
					  );
				}

				$count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
				if ($count_s) {
					$count = Auth_OpenID::intval($count_s);
					if (($count === false) &&
						($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
						$count = $count_s;
					}
				} else {
					$count = 1;
				}

				if ($count === false) {
					return new Auth_OpenID_AX_Error(
					  sprintf("Integer value expected for %s, got %s",
							  'count.' . $alias, $count_s));
				}

				$attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count,
														  false, $alias);

				if (Auth_OpenID_AX::isError($attrinfo)) {
					return $attrinfo;
				}

				$this->add($attrinfo);
			}
		}

		$required = Auth_OpenID_AX_toTypeURIs($aliases,
						 Auth_OpenID::arrayGet($ax_args, 'required'));

		foreach ($required as $type_uri) {
			$attrib =& $this->requested_attributes[$type_uri];
			$attrib->required = true;
		}

		$if_available = Auth_OpenID_AX_toTypeURIs($aliases,
							 Auth_OpenID::arrayGet($ax_args, 'if_available'));

		$all_type_uris = array_merge($required, $if_available);

		foreach ($aliases->iterNamespaceURIs() as $type_uri) {
			if (!in_array($type_uri, $all_type_uris)) {
				return new Auth_OpenID_AX_Error(
				  sprintf('Type URI %s was in the request but not ' .
						  'present in "required" or "if_available"',
						  $type_uri));

			}
		}

		$this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');

		return true;
	}

	/**
	 * Iterate over the AttrInfo objects that are contained in this
	 * fetch_request.
	 */
	function iterAttrs()
	{
		return array_values($this->requested_attributes);
	}

	function iterTypes()
	{
		return array_keys($this->requested_attributes);
	}

	/**
	 * Is the given type URI present in this fetch_request?
	 */
	function contains($type_uri)
	{
		return in_array($type_uri, $this->iterTypes());
	}
}

/**
 * An abstract class that implements a message that has attribute keys
 * and values. It contains the common code between fetch_response and
 * store_request.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {

	function Auth_OpenID_AX_KeyValueMessage()
	{
		$this->data = array();
	}

	/**
	 * Add a single value for the given attribute type to the
	 * message. If there are already values specified for this type,
	 * this value will be sent in addition to the values already
	 * specified.
	 *
	 * @param type_uri: The URI for the attribute
	 * @param value: The value to add to the response to the relying
	 * party for this attribute
	 * @return null
	 */
	function addValue($type_uri, $value)
	{
		if (!array_key_exists($type_uri, $this->data)) {
			$this->data[$type_uri] = array();
		}

		$values =& $this->data[$type_uri];
		$values[] = $value;
	}

	/**
	 * Set the values for the given attribute type. This replaces any
	 * values that have already been set for this attribute.
	 *
	 * @param type_uri: The URI for the attribute
	 * @param values: A list of values to send for this attribute.
	 */
	function setValues($type_uri, &$values)
	{
		$this->data[$type_uri] =& $values;
	}

	/**
	 * Get the extension arguments for the key/value pairs contained
	 * in this message.
	 *
	 * @param aliases: An alias mapping. Set to None if you don't care
	 * about the aliases for this request.
	 *
	 * @access private
	 */
	function _getExtensionKVArgs(&$aliases)
	{
		if ($aliases === null) {
			$aliases = new Auth_OpenID_NamespaceMap();
		}

		$ax_args = array();

		foreach ($this->data as $type_uri => $values) {
			$alias = $aliases->add($type_uri);

			$ax_args['type.' . $alias] = $type_uri;
			$ax_args['count.' . $alias] = strval(count($values));

			foreach ($values as $i => $value) {
			  $key = sprintf('value.%s.%d', $alias, $i + 1);
			  $ax_args[$key] = $value;
			}
		}

		return $ax_args;
	}

	/**
	 * Parse attribute exchange key/value arguments into this object.
	 *
	 * @param ax_args: The attribute exchange fetch_response
	 * arguments, with namespacing removed.
	 *
	 * @return Auth_OpenID_AX_Error or true
	 */
	function parseExtensionArgs($ax_args)
	{
		$result = $this->_checkMode($ax_args);
		if (Auth_OpenID_AX::isError($result)) {
			return $result;
		}

		$aliases = new Auth_OpenID_NamespaceMap();

		foreach ($ax_args as $key => $value) {
			if (strpos($key, 'type.') === 0) {
				$type_uri = $value;
				$alias = substr($key, 5);

				$result = Auth_OpenID_AX_checkAlias($alias);

				if (Auth_OpenID_AX::isError($result)) {
					return $result;
				}

				$alias = $aliases->addAlias($type_uri, $alias);

				if ($alias === null) {
					return new Auth_OpenID_AX_Error(
					  sprintf("Could not add alias %s for URI %s",
							  $alias, $type_uri)
					  );
				}
			}
		}

		foreach ($aliases->iteritems() as $pair) {
			list($type_uri, $alias) = $pair;

			if (array_key_exists('count.' . $alias, $ax_args)) {

				$count_key = 'count.' . $alias;
				$count_s = $ax_args[$count_key];

				$count = Auth_OpenID::intval($count_s);

				if ($count === false) {
					return new Auth_OpenID_AX_Error(
					  sprintf("Integer value expected for %s, got %s",
							  'count. %s' . $alias, $count_s,
							  Auth_OpenID_AX_UNLIMITED_VALUES)
													);
				}

				$values = array();
				for ($i = 1; $i < $count + 1; $i++) {
					$value_key = sprintf('value.%s.%d', $alias, $i);

					if (!array_key_exists($value_key, $ax_args)) {
					  return new Auth_OpenID_AX_Error(
						sprintf(
								"No value found for key %s",
								$value_key));
					}

					$value = $ax_args[$value_key];
					$values[] = $value;
				}
			} else {
				$key = 'value.' . $alias;

				if (!array_key_exists($key, $ax_args)) {
				  return new Auth_OpenID_AX_Error(
					sprintf(
							"No value found for key %s",
							$key));
				}

				$value = $ax_args['value.' . $alias];

				if ($value == '') {
					$values = array();
				} else {
					$values = array($value);
				}
			}

			$this->data[$type_uri] = $values;
		}

		return true;
	}

	/**
	 * Get a single value for an attribute. If no value was sent for
	 * this attribute, use the supplied default. If there is more than
	 * one value for this attribute, this method will fail.
	 *
	 * @param type_uri: The URI for the attribute
	 * @param default: The value to return if the attribute was not
	 * sent in the fetch_response.
	 *
	 * @return $value Auth_OpenID_AX_Error on failure or the value of
	 * the attribute in the fetch_response message, or the default
	 * supplied
	 */
	function getSingle($type_uri, $default=null)
	{
		$values = Auth_OpenID::arrayGet($this->data, $type_uri);
		if (!$values) {
			return $default;
		} else if (count($values) == 1) {
			return $values[0];
		} else {
			return new Auth_OpenID_AX_Error(
			  sprintf('More than one value present for %s',
					  $type_uri)
			  );
		}
	}

	/**
	 * Get the list of values for this attribute in the
	 * fetch_response.
	 *
	 * XXX: what to do if the values are not present? default
	 * parameter? this is funny because it's always supposed to return
	 * a list, so the default may break that, though it's provided by
	 * the user's code, so it might be okay. If no default is
	 * supplied, should the return be None or []?
	 *
	 * @param type_uri: The URI of the attribute
	 *
	 * @return $values The list of values for this attribute in the
	 * response. May be an empty list.  If the attribute was not sent
	 * in the response, returns Auth_OpenID_AX_Error.
	 */
	function get($type_uri)
	{
		if (array_key_exists($type_uri, $this->data)) {
			return $this->data[$type_uri];
		} else {
			return new Auth_OpenID_AX_Error(
			  sprintf("Type URI %s not found in response",
					  $type_uri)
			  );
		}
	}

	/**
	 * Get the number of responses for a particular attribute in this
	 * fetch_response message.
	 *
	 * @param type_uri: The URI of the attribute
	 *
	 * @returns int The number of values sent for this attribute.  If
	 * the attribute was not sent in the response, returns
	 * Auth_OpenID_AX_Error.
	 */
	function count($type_uri)
	{
		if (array_key_exists($type_uri, $this->data)) {
			return count($this->get($type_uri));
		} else {
			return new Auth_OpenID_AX_Error(
			  sprintf("Type URI %s not found in response",
					  $type_uri)
			  );
		}
	}
}

/**
 * A fetch_response attribute exchange message.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
	var $mode = 'fetch_response';

	function Auth_OpenID_AX_FetchResponse($update_url=null)
	{
		$this->Auth_OpenID_AX_KeyValueMessage();
		$this->update_url = $update_url;
	}

	/**
	 * Serialize this object into arguments in the attribute exchange
	 * namespace
	 *
	 * @return $args The dictionary of unqualified attribute exchange
	 * arguments that represent this fetch_response, or
	 * Auth_OpenID_AX_Error on error.
	 */
	function getExtensionArgs($request=null)
	{
		$aliases = new Auth_OpenID_NamespaceMap();

		$zero_value_types = array();

		if ($request !== null) {
			// Validate the data in the context of the request (the
			// same attributes should be present in each, and the
			// counts in the response must be no more than the counts
			// in the request)

			foreach ($this->data as $type_uri => $unused) {
				if (!$request->contains($type_uri)) {
					return new Auth_OpenID_AX_Error(
					  sprintf("Response attribute not present in request: %s",
							  $type_uri)
					  );
				}
			}

			foreach ($request->iterAttrs() as $attr_info) {
				// Copy the aliases from the request so that reading
				// the response in light of the request is easier
				if ($attr_info->alias === null) {
					$aliases->add($attr_info->type_uri);
				} else {
					$alias = $aliases->addAlias($attr_info->type_uri,
												$attr_info->alias);

					if ($alias === null) {
						return new Auth_OpenID_AX_Error(
						  sprintf("Could not add alias %s for URI %s",
								  $attr_info->alias, $attr_info->type_uri)
						  );
					}
				}

				if (array_key_exists($attr_info->type_uri, $this->data)) {
					$values = $this->data[$attr_info->type_uri];
				} else {
					$values = array();
					$zero_value_types[] = $attr_info;
				}

				if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) &&
					($attr_info->count < count($values))) {
					return new Auth_OpenID_AX_Error(
					  sprintf("More than the number of requested values " .
							  "were specified for %s",
							  $attr_info->type_uri)
					  );
				}
			}
		}

		$kv_args = $this->_getExtensionKVArgs($aliases);

		// Add the KV args into the response with the args that are
		// unique to the fetch_response
		$ax_args = $this->_newArgs();

		// For each requested attribute, put its type/alias and count
		// into the response even if no data were returned.
		foreach ($zero_value_types as $attr_info) {
			$alias = $aliases->getAlias($attr_info->type_uri);
			$kv_args['type.' . $alias] = $attr_info->type_uri;
			$kv_args['count.' . $alias] = '0';
		}

		$update_url = null;
		if ($request) {
			$update_url = $request->update_url;
		} else {
			$update_url = $this->update_url;
		}

		if ($update_url) {
			$ax_args['update_url'] = $update_url;
		}

		Auth_OpenID::update(&$ax_args, $kv_args);

		return $ax_args;
	}

	/**
	 * @return $result Auth_OpenID_AX_Error on failure or true on
	 * success.
	 */
	function parseExtensionArgs($ax_args)
	{
		$result = parent::parseExtensionArgs($ax_args);

		if (Auth_OpenID_AX::isError($result)) {
			return $result;
		}

		$this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');

		return true;
	}

	/**
	 * Construct a FetchResponse object from an OpenID library
	 * SuccessResponse object.
	 *
	 * @param success_response: A successful id_res response object
	 *
	 * @param signed: Whether non-signed args should be processsed. If
	 * True (the default), only signed arguments will be processsed.
	 *
	 * @return $response A FetchResponse containing the data from the
	 * OpenID message
	 */
	function fromSuccessResponse($success_response, $signed=true)
	{
		$obj = new Auth_OpenID_AX_FetchResponse();
		if ($signed) {
			$ax_args = $success_response->getSignedNS($obj->ns_uri);
		} else {
			$ax_args = $success_response->message->getArgs($obj->ns_uri);
		}
		if ($ax_args === null || Auth_OpenID::isFailure($ax_args) ||
			  sizeof($ax_args) == 0) {
			return null;
		}

		$result = $obj->parseExtensionArgs($ax_args);
		if (Auth_OpenID_AX::isError($result)) {
			#XXX log me
			return null;
		}
		return $obj;
	}
}

/**
 * A store request attribute exchange message representation.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
	var $mode = 'store_request';

	/**
	 * @param array $aliases The namespace aliases to use when making
	 * this store response. Leave as None to use defaults.
	 */
	function getExtensionArgs($aliases=null)
	{
		$ax_args = $this->_newArgs();
		$kv_args = $this->_getExtensionKVArgs($aliases);
		Auth_OpenID::update(&$ax_args, $kv_args);
		return $ax_args;
	}
}

/**
 * An indication that the store request was processed along with this
 * OpenID transaction.  Use make(), NOT the constructor, to create
 * response objects.
 *
 * @package OpenID
 */
class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
	var $SUCCESS_MODE = 'store_response_success';
	var $FAILURE_MODE = 'store_response_failure';

	/**
	 * Returns Auth_OpenID_AX_Error on error or an
	 * Auth_OpenID_AX_StoreResponse object on success.
	 */
	function &make($succeeded=true, $error_message=null)
	{
		if (($succeeded) && ($error_message !== null)) {
			return new Auth_OpenID_AX_Error('An error message may only be '.
									'included in a failing fetch response');
		}

		return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
	}

	function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
	{
		if ($succeeded) {
			$this->mode = $this->SUCCESS_MODE;
		} else {
			$this->mode = $this->FAILURE_MODE;
		}

		$this->error_message = $error_message;
	}

	/**
	 * Was this response a success response?
	 */
	function succeeded()
	{
		return $this->mode == $this->SUCCESS_MODE;
	}

	function getExtensionArgs()
	{
		$ax_args = $this->_newArgs();
		if ((!$this->succeeded()) && $this->error_message) {
			$ax_args['error'] = $this->error_message;
		}

		return $ax_args;
	}
}

?>